package cn.acyou.actions.copyMappingPath;

import cn.acyou.utils.*;
import com.intellij.lang.jvm.JvmAnnotation;
import com.intellij.lang.jvm.JvmParameter;
import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute;
import com.intellij.lang.jvm.types.JvmType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.PropertiesUtil;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.yaml.snakeyaml.Yaml;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author youfang
 * @version [1.0.0, 2021/12/29 10:00]
 **/
public class CopyMappingPathAction extends AnAction {
    private List<String> urlParam = new ArrayList<>();
    private List<PsiClass> paramClass = new ArrayList<>();
    private String classMappingValueFinal = "";
    private String methodMappingFinal = "";

    @Override
    public void update(AnActionEvent e) {
        //————————————————————————————重置参数
        urlParam.clear();
        paramClass.clear();
        classMappingValueFinal = "";
        methodMappingFinal = "";
        //————————————————————————————重置参数
        DataContext dataContext = e.getDataContext();
        Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
        PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(dataContext);
        boolean enableIt = false;
        if (psiFile instanceof PsiJavaFile && editor != null) {
            Document document = editor.getDocument();
            int lineNumber = document.getLineNumber(editor.getSelectionModel().getLeadSelectionOffset());
            int lineStartOffset = document.getLineStartOffset(lineNumber);
            int lineEndOffset = document.getLineEndOffset(lineNumber);
            String text = document.getText(TextRange.create(lineStartOffset, lineEndOffset));
            if (isMapping(text)) {
                try {
                    //获取method
                    final PsiElement context = psiFile.findElementAt(lineEndOffset).getContext().getContext();
                    PsiMethod psiMethod = (PsiMethod) context;
                    final PsiAnnotation classMapping = RestUtil.getClassAnnotation(psiMethod.getContainingClass(), "org.springframework.web.bind.annotation.RequestMapping", "RequestMapping");
                    final List<PsiAnnotation> methodAnnotations = RestUtil.getMethodAnnotations(psiMethod);
                    String classMappingValue= "";
                    String methodMapping = "";
                    for (PsiAnnotation methodAnnotation : methodAnnotations) {
                        final String mappingValue = getMappingValue(methodAnnotation);
                        if (mappingValue != null && mappingValue.length() > 0) {
                            methodMapping = mappingValue;
                            break;
                        }
                    }
                    if (classMapping != null) {
                        classMappingValue = getMappingValue(classMapping);
                    }
                    //参数

                    JvmParameter[] parameters = psiMethod.getParameters();
                    if (parameters.length > 0) {
                        for (int i = 0; i < parameters.length; i++) {
                            JvmParameter parameter = parameters[i];
                            JvmAnnotation[] annotations = parameter.getAnnotations();
                            boolean hasRequestBody = false;
                            if (annotations.length > 0) {
                                //如果字段上存在RequestBody注解则跳过
                                for (JvmAnnotation annotation : annotations) {
                                    if ("org.springframework.web.bind.annotation.RequestBody".equals(annotation.getQualifiedName())) {
                                        hasRequestBody = true;
                                        break;
                                    }
                                }
                            }
                            if (hasRequestBody) {
                                continue;
                            }
                            JvmType type = parameter.getType();
                            if (type instanceof PsiClassReferenceType) {
                                String typeName = ((PsiClassReferenceType) type).getName();
                                if (CommonUtil.baseType.contains(typeName)) {
                                    urlParam.add(parameter.getName());
                                }else {
                                    //对象类型 加入待后续处理
                                    PsiClass psiClass = PsiUtil.resolveClassInType((PsiType) type);
                                    paramClass.add(psiClass);
                                }
                            }
                            if (type instanceof PsiPrimitiveType) {
                                String typeName = ((PsiPrimitiveType) type).getName();
                                if (CommonUtil.baseType.contains(typeName)) {
                                    urlParam.add(parameter.getName());
                                }
                            }
                        }
                    }
                    classMappingValueFinal = classMappingValue;
                    methodMappingFinal = methodMapping;
                    enableIt = true;
                } catch (Exception ex) {
                    //ignore
                    ex.printStackTrace();
                }
            }
        }
        e.getPresentation().setEnabledAndVisible(enableIt);
    }

    private boolean isImplementSerializable(PsiClass psiClass){
        if (psiClass == null) {
            return false;
        }
        PsiClassType[] implementsListTypes = PsiClassImplUtil.getImplementsListTypes(psiClass);
        for (PsiClassType implementsListType : implementsListTypes) {
            if (implementsListType.getName().equals("Serializable")) {
                return true;
            }
        }
        PsiClass psiClassParent = PsiClassImplUtil.getSuperClass(psiClass);
        if (psiClassParent != null) {
            return isImplementSerializable(psiClassParent);
        }
        return false;
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        try {
            ApplicationManager.getApplication().runReadAction(()->{
                try {
                    //处理
                    for (PsiClass psiClass : paramClass) {
                        boolean implementSerializable = isImplementSerializable(psiClass);
                        if (implementSerializable) {
                            PsiField[] allFields = PsiClassImplUtil.getAllFields(psiClass);
                            for (PsiField psiField : allFields) {
                                PsiModifierList modifierList = psiField.getModifierList();
                                if (modifierList != null && modifierList.getText().contains("static")) {
                                    //ignore static filed
                                    continue;
                                }
                                urlParam.add(psiField.getName());
                            }
                        }
                    }

                    String host = findProjectHostAndContext(project);
                    String COPY_PATH = host + ("/" + classMappingValueFinal + "/" + methodMappingFinal).replaceAll("//", "/");
                    if (urlParam.size() > 0){
                        StringBuilder query = new StringBuilder();
                        for (String s : urlParam) {
                            if (query.length() == 0) {
                                query = new StringBuilder(s + "=");
                            }else {
                                query.append("&").append(s).append("=");
                            }
                        }
                        COPY_PATH = COPY_PATH + "?" + query;
                    }
                    StringSelection selection = new StringSelection(COPY_PATH);
                    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                    clipboard.setContents(selection, selection);
                    String message = "访问路径识别成功，已复制到剪切板！";
                    MessageUtil.notifyProject(message, project);
                }
                catch(Exception exe) {
                    exe.printStackTrace();
                }
            });
        }catch (Exception exception) {
            //ignore
        }
    }

    private String findProjectHostAndContext(Project project) {
        return CommonCache.getAndCache(project.getBasePath() + ":serverHostAndContextPath", (o) ->{
            String defaultHost = "http://localhost:";
            String defaultPort = "8080";
            String defaultContext = "";
            try {
                //文件查询操作需要使用ProgressManager来调用，否则会SlowOperations.assertSlowOperationsAreAllowed
                //参考地址：https://github.com/asciidoctor/asciidoctor-intellij-plugin/issues/815
                String[] config = findConfig(project);
                String port = config[0];
                String path = config[1];
                if (port.length() > 0) {
                    defaultPort = port;
                }
                if (path.length() > 0) {
                    String path2 = path.replaceFirst("/", "");
                    if (path2.length() > 0) {
                        defaultContext = "/" + path2;
                    }
                }
                String host = defaultHost + defaultPort + defaultContext;
                return host;
            } catch (Exception e) {
                //ignore it
            }
            return defaultHost + defaultPort;
        });
    }

    private String[] findConfig(Project project)  {
        //查找文件也可以使用：
        //PsiFile[] filesByName = FilenameIndex.getFilesByName(project, "application.yml", GlobalSearchScope.projectScope(project));
        Map<String, String> applicationDevProperties = CommonCache.getAndCacheObj(project.getBasePath() + "application-dev.properties", (k) -> {
            try {
                //application-dev.properties
                PsiFile[] fileInProject = FindUtil.findProjectFileByName(project, "application-dev.properties");
                if (fileInProject.length > 0) {
                    return PropertiesUtil.loadProperties(new FileReader(fileInProject[0].getVirtualFile().getPath()));
                }
            } catch (Exception ex) {
                //ignore
            }
            return null;
        });

        Map<String, Object> applicationDevYml = CommonCache.getAndCacheObj(project.getBasePath() + "application-dev.yml", (k) -> {
            try {
                //application-dev.yml
                PsiFile[] fileInProject = FindUtil.findProjectFileByName(project, "application-dev.yml");
                if (fileInProject.length > 0) {
                    Map<String, Object> load = new Yaml().load(fileInProject[0].getVirtualFile().getInputStream());
                    return load;
                }
            } catch (Exception ex) {
                //ignore
            }
            return null;
        });

        Map<String, String> applicationProperties = CommonCache.getAndCacheObj(project.getBasePath() + "application.properties", (k) -> {
            try {
                //application-dev.properties
                PsiFile[] fileInProject = FindUtil.findProjectFileByName(project, "application.properties");
                if (fileInProject.length > 0) {
                    return PropertiesUtil.loadProperties(new FileReader(fileInProject[0].getVirtualFile().getPath()));
                }
            } catch (Exception ex) {
                //ignore
            }
            return null;
        });

        Map<String, Object> applicationYml = CommonCache.getAndCacheObj(project.getBasePath() + "application.yml", (k) -> {
            try {
                //application-dev.yml
                PsiFile[] fileInProject = FindUtil.findProjectFileByName(project, "application.yml");
                if (fileInProject.length > 0) {
                    Map<String, Object> load = new Yaml().load(fileInProject[0].getVirtualFile().getInputStream());
                    return load;
                }
            } catch (Exception ex) {
                //ignore
            }
            return null;
        });
        Map<String, String> bootstrapProperties = CommonCache.getAndCacheObj(project.getBasePath() + "bootstrap.properties", (k) -> {
            try {
                //bootstrap.properties
                PsiFile[] fileInProject = FindUtil.findProjectFileByName(project, "bootstrap.properties");
                if (fileInProject.length > 0) {
                    return PropertiesUtil.loadProperties(new FileReader(fileInProject[0].getVirtualFile().getPath()));
                }
            } catch (Exception ex) {
                //ignore
            }
            return null;
        });

        Map<String, Object> bootstrapYml = CommonCache.getAndCacheObj(project.getBasePath() + "bootstrap.yml", (k) -> {
            try {
                //bootstrap.yml
                PsiFile[] fileInProject = FindUtil.findProjectFileByName(project, "bootstrap.yml");
                if (fileInProject.length > 0) {
                    Map<String, Object> load = new Yaml().load(fileInProject[0].getVirtualFile().getInputStream());
                    return load;
                }
            } catch (Exception ex) {
                //ignore
            }
            return null;
        });
        //last find config
        String[] res = new String[2];
        //1
        String serverPort1 = getPropertiesValue(applicationProperties, "server.port");
        if (serverPort1 != null) {
            res[0] = serverPort1;
        }
        String serverPort2 = getYmlValue(applicationYml, "server.port");
        if (serverPort2 != null) {
            res[0] = serverPort2;
        }
        String serverPort3 = getPropertiesValue(applicationDevProperties, "server.port");
        if (serverPort3 != null) {
            res[0] = serverPort3;
        }
        String serverPort4 = getYmlValue(applicationDevYml, "server.port");
        if (serverPort4 != null) {
            res[0] = serverPort4;
        }
        String serverPort5 = getPropertiesValue(bootstrapProperties, "server.port");
        if (serverPort5 != null) {
            res[0] = serverPort5;
        }
        String serverPort6 = getYmlValue(bootstrapYml, "server.port");
        if (serverPort6 != null) {
            res[0] = serverPort6;
        }
        //2
        String context3 = getPropertiesValue(applicationProperties, "server.servlet.context-path");
        if (context3 != null) {
            res[1] = context3;
        }
        String context4 = getYmlValue(applicationYml, "server.servlet.context-path");
        if (context4 != null) {
            res[1] = context4;
        }
        String context1 = getPropertiesValue(applicationDevProperties, "server.servlet.context-path");
        if (context1 != null) {
            res[1] = context1;
        }
        String context2 = getYmlValue(applicationDevYml, "server.servlet.context-path");
        if (context2 != null) {
            res[1] = context2;
        }
        String context5 = getPropertiesValue(bootstrapProperties, "server.servlet.context-path");
        if (context5 != null) {
            res[1] = context5;
        }
        String context6 = getYmlValue(bootstrapYml, "server.servlet.context-path");
        if (context6 != null) {
            res[1] = context6;
        }
        return res;
    }

    private String getPropertiesValue(Map<String, String> load, String key){
        if (load != null && !load.isEmpty()) {
            return load.get(key);
        }
        return null;
    }

    private String getYmlValue(Map<String, Object> load, String key){
        if (load != null && !load.isEmpty()) {
            if (key.contains(".")) {
                String first = key.substring(0, key.indexOf("."));
                String last = key.substring(key.indexOf(".") + 1);
                Object o = load.get(first);
                if (o instanceof  Map) {
                    Map<String, Object> firstMap = (Map) o;
                    return getYmlValue(firstMap, last);
                }
            }else {
                Object o = load.get(key);
                if (o != null) {
                    return String.valueOf(o);
                }
            }
        }
        return null;
    }

    @Deprecated
    private String[] getServerPortAndPath(PsiFile file) throws Exception {
        String[] str = new String[2];
        if (file.getVirtualFile().getFileType().getName().equals("YAML")) {
            Map<String, Object> load = new Yaml().load(file.getVirtualFile().getInputStream());
            Object server = load.get("server");
            if (server != null) {
                Map<String, Object> serverMap = (Map) server;
                Object port = serverMap.get("port");
                if (port != null) {
                    str[0] = String.valueOf(port);
                }
                Object servlet = serverMap.get("servlet");
                if (servlet != null) {
                    Map<String, Object> servletMap = (Map) servlet;
                    Object contextPath = servletMap.get("context-path");
                    if (contextPath != null) {
                        str[1] = String.valueOf(contextPath);
                    }
                }
            }
        }
        if (file.getVirtualFile().getFileType().getName().equals("Properties")) {
            Map<String, String> load = PropertiesUtil.loadProperties(new FileReader(file.getVirtualFile().getPath()));
            String port = load.get("server.port");
            if (port != null) {
                str[0] =  port;
            }
            String contextPath = load.get("server.servlet.context-path");
            if (contextPath != null) {
                str[1] =  contextPath;
            }
        }
        return str;
    }

    /**
     * 判断是不是 mapping
     *
     * @param text 文本
     * @return boolean
     */
    private boolean isMapping(String text) {
        final List<String> mappingList = CommonUtil.MAPPING_LIST;
        for (String s : mappingList) {
            if (text.indexOf(s) > 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * 得到映射值
     *
     * @param annotation 注释
     * @return {@link String}
     */
    private String getMappingValue(PsiAnnotation annotation) {
        final String qualifiedName = annotation.getQualifiedName();
        if (isMapping(qualifiedName)) {
            for (JvmAnnotationAttribute attribute : annotation.getAttributes()) {
                if (attribute.getAttributeName().equals("value")) {
                    return (String) RestUtil.getAttributeValue(attribute.getAttributeValue());
                }
            }
        }
        return null;
    }


}
